/*
 * $Id: AbstractActionExt.java 4158 2012-02-03 18:29:40Z kschaefe $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.jdesktop.swingx.action;

import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeListener;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.KeyStroke;

/**
 * Extends the concept of the Action to include toggle or group states.
 * <p>
 * SwingX 1.6.3 updates {@code AbstractActionExt} to use new features of
 * {@link Action} that were added in {@code Java 1.6}. The selection is now
 * managed with {@link Action#SELECTED_KEY}, which allows the action to
 * correctly configured Swing buttons. The {@link #LARGE_ICON} has also been
 * changed to correspond to {@link Action#LARGE_ICON_KEY}.
 * 
 */
public abstract class AbstractActionExt extends AbstractAction implements ItemListener {

	/**
	 * The key for the large icon
	 * <p>
	 * As of SwingX 1.6.3 is now has the same value as
	 * {@link Action#LARGE_ICON_KEY}, which is new to 1.6.
	 */
	public static final String LARGE_ICON = Action.LARGE_ICON_KEY;

	/**
	 * The key for the button group
	 */
	public static final String GROUP = "__Group__";

	/**
	 * The key for the flag which indicates that this is a state type.
	 */
	public static final String IS_STATE = "__State__";

	/**
	 * Default constructor, does nothing.
	 */
	public AbstractActionExt() {
		this((String) null);
	}

	/**
	 * Copy constructor copies the state.
	 */
	public AbstractActionExt(AbstractActionExt action) {
		Object[] keys = action.getKeys();
		for (int i = 0; i < keys.length; i++) {
			putValue((String) keys[i], action.getValue((String) keys[i]));
		}
		this.enabled = action.enabled;

		// Copy change listeners.
		PropertyChangeListener[] listeners = action.getPropertyChangeListeners();
		for (int i = 0; i < listeners.length; i++) {
			addPropertyChangeListener(listeners[i]);
		}
	}

	public AbstractActionExt(String name) {
		super(name);
	}

	public AbstractActionExt(String name, Icon icon) {
		super(name, icon);
	}

	/**
	 * Constructs an Action with the label and command
	 *
	 * @param name
	 *            name of the action usually used as a label
	 * @param command
	 *            command key of the action
	 */
	public AbstractActionExt(String name, String command) {
		this(name);
		setActionCommand(command);
	}

	/**
	 * @param name
	 *            display name of the action
	 * @param command
	 *            the value of the action command key
	 * @param icon
	 *            icon to display
	 */
	public AbstractActionExt(String name, String command, Icon icon) {
		super(name, icon);
		setActionCommand(command);
	}

	/**
	 * Returns a short description of the action.
	 *
	 * @return the short description or null
	 */
	public String getShortDescription() {
		return (String) getValue(Action.SHORT_DESCRIPTION);
	}

	/**
	 * Sets the short description of the action. This will also set the long
	 * description value is it is null.
	 * <p>
	 * This is a convenience method for <code>putValue</code> with the
	 * <code>Action.SHORT_DESCRIPTION</code> key.
	 *
	 * @param desc
	 *            the short description; can be <code>null</code>w
	 * @see Action#SHORT_DESCRIPTION
	 * @see Action#putValue
	 */
	public void setShortDescription(String desc) {
		putValue(Action.SHORT_DESCRIPTION, desc);
		if (desc != null && getLongDescription() == null) {
			setLongDescription(desc);
		}
	}

	/**
	 * Returns a long description of the action.
	 *
	 * @return the long description or null
	 */
	public String getLongDescription() {
		return (String) getValue(Action.LONG_DESCRIPTION);
	}

	/**
	 * Sets the long description of the action. This will also set the value of
	 * the short description if that value is null.
	 * <p>
	 * This is a convenience method for <code>putValue</code> with the
	 * <code>Action.LONG_DESCRIPTION</code> key.
	 *
	 * @param desc
	 *            the long description; can be <code>null</code>
	 * @see Action#LONG_DESCRIPTION
	 * @see Action#putValue
	 */
	public void setLongDescription(String desc) {
		putValue(Action.LONG_DESCRIPTION, desc);
		if (desc != null && getShortDescription() == null) {
			setShortDescription(desc);
		}
	}

	/**
	 * Returns a small icon which represents the action.
	 *
	 * @return the small icon or null
	 */
	public Icon getSmallIcon() {
		return (Icon) getValue(SMALL_ICON);
	}

	/**
	 * Sets the small icon which represents the action.
	 * <p>
	 * This is a convenience method for <code>putValue</code> with the
	 * <code>Action.SMALL_ICON</code> key.
	 *
	 * @param icon
	 *            the small icon; can be <code>null</code>
	 * @see Action#SMALL_ICON
	 * @see Action#putValue
	 */
	public void setSmallIcon(Icon icon) {
		putValue(SMALL_ICON, icon);
	}

	/**
	 * Returns a large icon which represents the action.
	 *
	 * @return the large icon or null
	 */
	public Icon getLargeIcon() {
		return (Icon) getValue(LARGE_ICON);
	}

	/**
	 * Sets the large icon which represents the action.
	 * <p>
	 * This is a convenience method for <code>putValue</code> with the
	 * <code>LARGE_ICON</code> key.
	 *
	 * @param icon
	 *            the large icon; can be <code>null</code>
	 * @see #LARGE_ICON
	 * @see Action#putValue
	 */
	public void setLargeIcon(Icon icon) {
		putValue(LARGE_ICON, icon);
	}

	/**
	 * Sets the name of the action.
	 * <p>
	 * This is a convenience method for <code>putValue</code> with the
	 * <code>Action.NAME</code> key.
	 *
	 * @param name
	 *            the name of the action; can be <code>null</code>
	 * @see Action#NAME
	 * @see Action#putValue
	 */
	public void setName(String name) {
		putValue(Action.NAME, name);
	}

	/**
	 * Returns the name of the action.
	 *
	 * @return the name of the action or null
	 */
	public String getName() {
		return (String) getValue(Action.NAME);
	}

	public void setMnemonic(String mnemonic) {
		if (mnemonic != null && mnemonic.length() > 0) {
			putValue(Action.MNEMONIC_KEY, new Integer(mnemonic.charAt(0)));
		}
	}

	/**
	 * Sets the mnemonic key code for the action.
	 * <p>
	 * This is a convenience method for <code>putValue</code> with the
	 * <code>Action.MNEMONIC_KEY</code> key.
	 * <p>
	 * This method does not validate the value. Please see
	 * {@link javax.swing.AbstractButton#setMnemonic(int)} for details
	 * concerning the value of the mnemonic.
	 *
	 * @param mnemonic
	 *            an int key code mnemonic or 0
	 * @see javax.swing.AbstractButton#setMnemonic(int)
	 * @see Action#MNEMONIC_KEY
	 * @see Action#putValue
	 */
	public void setMnemonic(int mnemonic) {
		putValue(Action.MNEMONIC_KEY, new Integer(mnemonic));
	}

	/**
	 * Return the mnemonic key code for the action.
	 *
	 * @return the mnemonic or 0
	 */
	public int getMnemonic() {
		Integer value = (Integer) getValue(Action.MNEMONIC_KEY);
		if (value != null) {
			return value.intValue();
		}
		return '\0';
	}

	/**
	 * Sets the action command key. The action command key is used to identify
	 * the action.
	 * <p>
	 * This is a convenience method for <code>putValue</code> with the
	 * <code>Action.ACTION_COMMAND_KEY</code> key.
	 *
	 * @param key
	 *            the action command
	 * @see Action#ACTION_COMMAND_KEY
	 * @see Action#putValue
	 */
	public void setActionCommand(String key) {
		putValue(Action.ACTION_COMMAND_KEY, key);
	}

	/**
	 * Returns the action command.
	 * 
	 * @return the action command or null
	 */
	public String getActionCommand() {
		return (String) getValue(Action.ACTION_COMMAND_KEY);
	}

	/**
	 * Returns the key stroke which represents an accelerator for the action.
	 *
	 * @return the key stroke or null
	 */
	public KeyStroke getAccelerator() {
		return (KeyStroke) getValue(Action.ACCELERATOR_KEY);
	}

	/**
	 * Sets the key stroke which represents an accelerator for the action.
	 * <p>
	 * This is a convenience method for <code>putValue</code> with the
	 * <code>Action.ACCELERATOR_KEY</code> key.
	 *
	 * @param key
	 *            the key stroke; can be <code>null</code>
	 * @see Action#ACCELERATOR_KEY
	 * @see Action#putValue
	 */
	public void setAccelerator(KeyStroke key) {
		putValue(Action.ACCELERATOR_KEY, key);
	}

	/**
	 * Sets the group identity of the state action. This is used to identify the
	 * action as part of a button group.
	 */
	public void setGroup(Object group) {
		putValue(GROUP, group);
	}

	public Object getGroup() {
		return getValue(GROUP);
	}

	/**
	 * Will perform cleanup on the object. Should be called when finished with
	 * the Action. This should be used if a new action is constructed from the
	 * properties of an old action. The old action properties should be
	 * disposed.
	 */
	public void dispose() {
		PropertyChangeListener[] listeners = getPropertyChangeListeners();
		for (int i = 0; i < listeners.length; i++) {
			removePropertyChangeListener(listeners[i]);
		}
	}

	// Properties etc....

	/**
	 * Indicates if this action has states. If this method returns true then the
	 * this will send ItemEvents to ItemListeners when the control constructed
	 * with this action in invoked.
	 *
	 * @return true if this can handle states
	 */
	public boolean isStateAction() {
		Boolean state = (Boolean) getValue(IS_STATE);
		if (state != null) {
			return state.booleanValue();
		}
		return false;
	}

	/**
	 * Set the state property to true.
	 */
	public void setStateAction() {
		setStateAction(true);
	}

	/**
	 * Set the state property.
	 *
	 * @param state
	 *            if true then this action will fire ItemEvents
	 */
	public void setStateAction(boolean state) {
		putValue(IS_STATE, Boolean.valueOf(state));
	}

	/**
	 * @return true if the action is in the selected state
	 */
	public boolean isSelected() {
		Boolean selected = (Boolean) getValue(SELECTED_KEY);

		if (selected == null) {
			return false;
		}

		return selected.booleanValue();
	}

	/**
	 * Changes the state of the action. This is a convenience method for
	 * updating the Action via the value map.
	 * 
	 * @param newValue
	 *            true to set the action as selected of the action.
	 * @see Action#SELECTED_KEY
	 */
	public void setSelected(boolean newValue) {
		putValue(SELECTED_KEY, newValue);
	}

	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer("[");
		// RG: Fix for J2SE 5.0; Can't cascade append() calls because
		// return type in StringBuffer and AbstractStringBuilder are different
		buffer.append(this.getClass().toString());
		buffer.append(":");
		try {
			Object[] keys = getKeys();
			for (int i = 0; i < keys.length; i++) {
				buffer.append(keys[i]);
				buffer.append('=');
				buffer.append(getValue((String) keys[i]).toString());
				if (i < keys.length - 1) {
					buffer.append(',');
				}
			}
			buffer.append(']');
		} catch (Exception ex) { // RG: append(char) throws IOException in J2SE
									// 5.0
			/** @todo Log it */
		}
		return buffer.toString();
	}

	/**
	 * Callback method as <code>ItemListener</code>. Updates internal state
	 * based on the given ItemEvent.
	 * <p>
	 * 
	 * Here: synchs selected property if isStateAction(), does nothing
	 * otherwise.
	 * 
	 * @param e
	 *            the ItemEvent fired by a ItemSelectable on changing the
	 *            selected state.
	 */
	@Override
	public void itemStateChanged(ItemEvent e) {
		if (isStateAction()) {
			setSelected(ItemEvent.SELECTED == e.getStateChange());
		}
	}
}
